package querier

import (
	"file-cleaner/internal/logger"
	"file-cleaner/internal/pkg/file_cleaner/pool"
	"file-cleaner/internal/pkg/file_cleaner/queue"
	"fmt"
	"os"
	"path/filepath"
	"sync"
)

type FileQuerier struct {
	queryTaskPool *pool.Pool
	dirPathQueue  queue.Queue
	filepathQueue queue.Queue
}

func NewFileQuerier(poolCap int, filepathQueue queue.Queue) *FileQuerier {
	queryTaskPool, err := pool.NewPool(poolCap)
	if err != nil {
		panic(err)
	}
	return &FileQuerier{
		queryTaskPool: queryTaskPool,
		dirPathQueue:  queue.NewQueue(),
		filepathQueue: filepathQueue,
	}
}

func (q *FileQuerier) Query(dirPath string, cascade bool) error {
	// Check dirInfo
	dirInfo, err := os.Stat(dirPath)
	if err != nil {
		return err
	}

	if !dirInfo.IsDir() {
		return fmt.Errorf("%v is not a directory", dirPath)
	}

	q.dirPathQueue.Add(dirPath)

	err = q.query(cascade)
	if err != nil {
		return err
	}

	return nil
}

func (q *FileQuerier) query(cascade bool) error {
	wg := new(sync.WaitGroup)

	for !q.dirPathQueue.IsEmpty() {
		item := q.dirPathQueue.Poll()
		dirPath, ok := item.(string)
		if ok {
			wg.Add(1)
			err := q.queryTaskPool.Submit(func() {
				defer wg.Done()
				err := q.parseDir(dirPath, cascade)
				if err != nil {
					logger.Warn(err)
				}
			})
			if err != nil {
				logger.Warn(err)
			}
		}
		if q.dirPathQueue.IsEmpty() {
			wg.Wait()
		}
	}
	return nil
}

func (q *FileQuerier) parseDir(dirPath string, cascade bool) error {
	tmpEntries, err := os.ReadDir(dirPath)
	if err != nil {
		return fmt.Errorf("failed to read directory: %v, cased by: %v", dirPath, err)
	}

	for _, entry := range tmpEntries {
		if entry.IsDir() {
			if !cascade {
				continue
			}

			subDirPath := filepath.Join(dirPath, entry.Name())
			q.dirPathQueue.Add(subDirPath)

		} else {
			logger.Debugf("Catch file: %v", filepath.Join(dirPath, entry.Name()))
			q.filepathQueue.Add(filepath.Join(dirPath, entry.Name()))
		}
	}

	return nil
}

func (q *FileQuerier) Done() {
	q.queryTaskPool.Wait()
	q.filepathQueue.Add(queue.Done)
}
